home *** CD-ROM | disk | FTP | other *** search
/ PC Format (PL) 2008 February / PC_Format_022008.iso / Internet / Mozilla Thunderbird wtyczki / foxclocks-2.2.23-fx+tb+sb.xpi / components / dataupdater.js next >
Encoding:
Text File  |  2007-10-21  |  22.7 KB  |  622 lines

  1. // FoxClocks extension for Mozilla Firefox/Thunderbird/Sunbird
  2. // Copyright (C) 2005-2007 Andy McDonald / www.stemhaus.com
  3. // For licensing terms, please refer to readme.txt in this extension's '.xpi'
  4. // package or its installation directory on your computer.
  5.  
  6. // AFM - from http://forums.mozillazine.org/viewtopic.php?t=308369
  7.  
  8. // ====================================================================================
  9. const CI = Components.interfaces, CC = Components.classes, CR = Components.results;
  10.  
  11. // ====================================================================================
  12. FoxClocks_UpdateManager.classID = Components.ID("93b54d56-d965-44e3-a989-09b1b260cd94");
  13. FoxClocks_UpdateManager.contractID = "@stemhaus.com/firefox/foxclocks/updatemanager;1";
  14. FoxClocks_UpdateManager.classDescription = "FoxClocks Update Manager";
  15.  
  16. // ====================================================================================
  17. function FoxClocks_UpdateManager()
  18. {
  19.     this.componentStarted = false;
  20.     
  21.     this._httpRequest = null;
  22.     this._logger = null;
  23.        this._utils = null;        
  24.     this._observerService = null;
  25.     this._timer = null;
  26.     this._isDevel = false;
  27.     this._noServerUpdateURL = null;
  28.     
  29.     this.updateIntervalSecs = null;
  30.     this.nextUpdateDate = null; // null if automatic updates disabled
  31.     this.lastUpdateResult = "NONE"; // "ERROR", "OK_NEW", "OK_NO"
  32.     this.lastUpdateDate = null;
  33.     this.lastUpdateAuto = null;
  34.     
  35.     this.SERVER_RESP_STATUS_OK = 0;
  36.     this.SERVER_RESP_STATUS_BAD_REQ = -1;
  37.     this.SERVER_UPDATE_NO_NEW = 0;
  38.     this.SERVER_UPDATE_NEW = 1;
  39.     
  40.     this.STATE_NOT_INIT = 0;
  41.     this.STATE_OPEN = 1;
  42.     this.STATE_SENT = 2;
  43.     this.STATE_RECEIVING = 3;
  44.     this.STATE_LOADED = 4;
  45.     this._stateString = ["STATE_NOT_INIT", "STATE_OPEN", "STATE_SENT", "STATE_RECEIVING", "STATE_LOADED"];
  46.     
  47.     this._builtin_localDataFile = null;
  48.     this._updated_localDataFile = null;    
  49.     
  50.     this.STATUS_HTTP_OK = 200;
  51. }
  52.  
  53. // ====================================================================================
  54. FoxClocks_UpdateManager.self = null;
  55.  
  56. // ====================================================================================
  57. FoxClocks_UpdateManager.prototype =
  58. {
  59.     // ====================================================================================
  60.     startup: function()
  61.     {            
  62.         FoxClocks_UpdateManager.self = this; // stupid
  63.         
  64.         this._httpRequest = CC["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(CI.nsIXMLHttpRequest);
  65.         this._logger = CC["@stemhaus.com/firefox/foxclocks/logger;1"].getService(CI.nsISupports).wrappedJSObject;
  66.         this._utils = CC["@stemhaus.com/firefox/foxclocks/utils;1"].getService(CI.nsISupports).wrappedJSObject;    
  67.         this._observerService = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService);
  68.         this._timer = CC["@mozilla.org/timer;1"].createInstance(CI.nsITimer);
  69.         
  70.         this._builtin_localDataFile = this._utils.getFoxClocksDir();
  71.         this._builtin_localDataFile.append("data");
  72.         this._builtin_localDataFile.append("zones.xml");
  73.  
  74.         this._updated_localDataFile = this._utils.getFoxClocksDir();        
  75.         this._updated_localDataFile.append("data");
  76.         this._updated_localDataFile.append("zones_update.xml");    
  77.             
  78.         this.componentStarted = true;
  79.         this._observerService.notifyObservers(this, "foxclocks", "component:startup");
  80.     },
  81.         
  82.     // ====================================================================================
  83.     shutdown: function()
  84.     {
  85.     },
  86.  
  87.     // ====================================================================================
  88.     onObserve: function(aSubject, aTopic, aData)
  89.     {
  90.     },
  91.     
  92.     // ====================================================================================
  93.     init: function(enabled, intervalSecs, prevUpdateSecs, isDevel, noServerUpdateURL)
  94.     {
  95.         this._logger.log("FoxClocks_UpdateManager::init(): " + enabled + " " + intervalSecs +
  96.             " " + prevUpdateSecs + " " + isDevel + " " + noServerUpdateURL);
  97.                 
  98.         this.updateIntervalSecs = intervalSecs;    
  99.         this.lastUpdateDate = new Date(prevUpdateSecs * 1000);
  100.         this._isDevel = isDevel;
  101.         this._noServerUpdateURL = noServerUpdateURL;
  102.         this._setNextUpdateDate(enabled);
  103.                 
  104.         // AFM - cancel pending request
  105.         //
  106.         if (enabled == false)
  107.             this._resetConnection();
  108.     },
  109.     
  110.     
  111.     // ====================================================================================
  112.     // AFM - implementing nsITimerCallback
  113.     //
  114.     notify: function(timer) { this.updateNow(true); },
  115.         
  116.     // ====================================================================================
  117.     updateNow: function(isAutoUpdate)
  118.     {
  119.         this.lastUpdateAuto = isAutoUpdate != null && isAutoUpdate == true;
  120.         
  121.         this._logger.log("+FoxClocks_UpdateManager::updateNow(): " + (this.lastUpdateAuto ? "automatic" : "manual"));
  122.         
  123.         try
  124.         {
  125.             var requestURL = this._noServerUpdateURL != null ? this._noServerUpdateURL : this._getRemoteDataURL();
  126.                         
  127.             // AFM - in order to get at onreadystatechange etc.
  128.             //
  129.             this._httpRequest.QueryInterface(CI.nsIJSXMLHttpRequest);
  130.             
  131.             this._httpRequest.open('GET', requestURL, true);
  132.             this._httpRequest.onreadystatechange = this._onXMLReadyStateChange; 
  133.              this._httpRequest.channel.loadFlags |= CI.nsIRequest.LOAD_BYPASS_CACHE; 
  134.              
  135.             try
  136.             {
  137.                 if (this._httpRequest.channel instanceof CI.nsISupportsPriority)
  138.                     this._httpRequest.channel.priority = CI.nsISupportsPriority.PRIORITY_LOWEST;
  139.             }
  140.             catch(e) {this._logger.log("FoxClocks_UpdateManager::updateNow(): no priority");}; 
  141.               
  142.               // AFM - seems to block if, e.g. file://
  143.               //
  144.             this._httpRequest.send(null);
  145.             this._logger.log("-FoxClocks_UpdateManager::updateNow(): success");
  146.         }
  147.         catch (ex)
  148.         {
  149.             this._logger.log("-FoxClocks_UpdateManager::updateNow(): failed: " + ex, this._logger.ERROR);
  150.             this._setUpdateComplete("ERROR");
  151.         }
  152.     },
  153.         
  154.     // ====================================================================================
  155.     _resetConnection: function()
  156.     {        
  157.         if (this._httpRequest.readyState == this.STATE_NOT_INIT)
  158.             return;
  159.         
  160.         this._logger.log("+FoxClocks_UpdateManager::_resetConnection(): state <" +
  161.                 this._stateString[this._httpRequest.readyState] + ">");
  162.                     
  163.         if (this._httpRequest.readyState != this.STATE_LOADED)
  164.         {
  165.             // AFM - abnormal reuse of this._httpRequest
  166.             //
  167.             this._logger.log("FoxClocks_UpdateManager::_resetConnection(): aborting current request with state: " +
  168.                 this._stateString[this._httpRequest.readyState], this._logger.WARN);
  169.         }
  170.         
  171.         this._httpRequest.abort();
  172.         
  173.         this._logger.log("-FoxClocks_UpdateManager::_resetConnection(): state <" +
  174.                 this._stateString[this._httpRequest.readyState] + ">");
  175.     },
  176.     
  177.     // ====================================================================================
  178.     _onXMLReadyStateChange: function()
  179.     {        
  180.         // AFM - get multiple STATE_RECEIVING
  181.         //
  182.         var self = FoxClocks_UpdateManager.self;
  183.         var state = self._httpRequest.readyState;
  184.         
  185.         if (state != self.STATE_RECEIVING)
  186.         {
  187.             self._logger.log("+FoxClocks_UpdateManager::_onXMLReadyStateChange(): state <" +
  188.                 self._stateString[state] + ">");
  189.         }
  190.                 
  191.         if (state != self.STATE_LOADED)
  192.             return;
  193.  
  194.         var updateResult = "ERROR";
  195.         var httpStatus = null;
  196.         
  197.         // AFM - make sure exception is due to accessing _httpRequest.status
  198.         //
  199.         try { httpStatus = self._httpRequest.status; }
  200.         catch (ex)
  201.         {
  202.             self._logger.log("FoxClocks_UpdateManager::_onXMLReadyStateChange(): timeout", self._logger.WARN);
  203.         }
  204.         
  205.         if (httpStatus != null)
  206.         {
  207.             // AFM - the response can be null when we abort a live request - state transitions from STATE_OPEN,
  208.             // to LOADED, to NOT_INIT
  209.             //
  210.             var doc = self._httpRequest.responseXML;
  211.             
  212.             // AFM - we get a 0 status for file:// requests, at least. In a feeble attempt to stop
  213.             // this biting us, we only accept 0 status when we're using the user-supplied
  214.             // no-server update URL. This will break if the _normal_ update URL is returned from file://
  215.             //
  216.             if (doc != null && (httpStatus == self.STATUS_HTTP_OK || (self._noServerUpdateURL != null && httpStatus == 0)))
  217.             {
  218.                 updateResult = self._processXMLResponse(self._httpRequest.responseXML);
  219.             }
  220.             else
  221.             {
  222.                 // AFM - statusText not necessarily available, even though status exists
  223.                 //
  224.                 var httpStatusText = "[status text unavailable]";
  225.                 try { httpStatusText = self._httpRequest.statusText; }
  226.                 catch (ex)
  227.                 {
  228.                     self._logger.log("FoxClocks_UpdateManager::_onXMLReadyStateChange(): status text unavailable");
  229.                 }
  230.                 
  231.                 self._logger.log("FoxClocks_UpdateManager::_onXMLReadyStateChange(): http status " +
  232.                     httpStatus + " (" +
  233.                     httpStatusText + "), doc is null: " + (doc == null), self._logger.WARN);    
  234.             }
  235.         }
  236.         
  237.         self._setUpdateComplete(updateResult);
  238.         
  239.         self._logger.log("-FoxClocks_UpdateManager::_onXMLReadyStateChange()");
  240.     },
  241.     
  242.     // ====================================================================================
  243.     _setUpdateComplete: function(updateResult)
  244.     {
  245.         this._logger.log("FoxClocks_UpdateManager::_setUpdateComplete(): result: " + updateResult);
  246.             
  247.         // AFM - do not reset the connection here. Connection is already 'done' even if it's LOADED
  248.         // (usually request complete) or OPEN (when file not found in send(), eg). The connection
  249.         // will be reset next time an update is started. Resetting here triggers stuff we don't want
  250.         // triggered
  251.         //
  252.         // AFM - update. No, reset it but disable the callback
  253.         //
  254.         
  255.         this._httpRequest.onreadystatechange = null; 
  256.         this._resetConnection();
  257.         this._httpRequest.onreadystatechange = this._onXMLReadyStateChange; 
  258.  
  259.         this.lastUpdateResult = updateResult;
  260.         this.lastUpdateDate = new Date();
  261.         this._setNextUpdateDate();
  262.         
  263.         this._observerService.notifyObservers(this, "foxclocks", "updatemanager:update-complete");
  264.     },
  265.     
  266.     // ====================================================================================
  267.     _setNextUpdateDate: function(enabled)
  268.     {
  269.         // AFM - depends on this.lastUpdateDate and this.updateIntervalSecs
  270.         //
  271.     
  272.         // AFM - use current enabled status
  273.         //
  274.         if (enabled == null)
  275.             enabled = (this.nextUpdateDate != null);
  276.                         
  277.         if (enabled == false)
  278.         {
  279.             this.nextUpdateDate = null;
  280.             this._timer.cancel();
  281.             this._logger.log("FoxClocks_UpdateManager::_setNextUpdateDate(): auto-update disabled");
  282.             return;
  283.         }
  284.             
  285.         var utcAsMillis = new Date().getTime();
  286.             
  287.         var nextUpdateAsMillis = this.lastUpdateDate.getTime() + this.updateIntervalSecs * 1000;
  288.         var millisToNextUpdate = Math.max(nextUpdateAsMillis - utcAsMillis, 0);
  289.  
  290.         var nextUpdateTimeMillis = millisToNextUpdate + utcAsMillis;
  291.         this.nextUpdateDate = new Date(nextUpdateTimeMillis);
  292.         
  293.         this._logger.log("FoxClocks_UpdateManager::_setNextUpdateDate(): auto-update enabled: next update: " +
  294.                 this.nextUpdateDate.toLocaleString());
  295.                         
  296.         this._timer.initWithCallback(this, millisToNextUpdate, CI.nsITimer.TYPE_ONE_SHOT);
  297.     },
  298.     
  299.     // ====================================================================================
  300.     _processXMLResponse: function(doc)
  301.     {
  302.         // AFM - returns true only on new data
  303.         //
  304.         var zoneManager = CC["@stemhaus.com/firefox/foxclocks/zonemanager;1"].
  305.             getService(CI.nsISupports).wrappedJSObject;
  306.  
  307.         var retVal = "ERROR";
  308.         var updateStatus = this.SERVER_UPDATE_NO_NEW;
  309.         
  310.         try
  311.         {
  312.             var root = doc.documentElement;
  313.             
  314.             // AFM - this can happen when a connection times out after a partial response
  315.             //
  316.             if (root.nodeName == "parsererror")
  317.             {
  318.                 this._logger.log("FoxClocks_UpdateManager::_processXMLResponse(): parsererror: " +
  319.                         root.firstChild.nodeValue, this._logger.ERROR);
  320.                 return retVal;
  321.             }
  322.             
  323.             if (this._noServerUpdateURL == null)
  324.             {
  325.                 // AFM - a response from the server, rather than raw data
  326.                 //
  327.                 var respStatusNode = this._utils.getFirstEltByTagAsNode(root, "response-status");
  328.         
  329.                 if (respStatusNode.getAttribute("code") != this.SERVER_RESP_STATUS_OK)
  330.                 {
  331.                     // AFM - should propagate this to user
  332.                     //
  333.                     var statusMsg = respStatusNode.getAttribute("msg");
  334.     
  335.                     this._logger.log("FoxClocks_UpdateManager::_processXMLResponse(): response from server indicates error: " +
  336.                         statusMsg, this._logger.ERROR);
  337.                         
  338.                     return retVal;
  339.                 }
  340.             
  341.                 var bodyNode = this._utils.getFirstEltByTagAsNode(root, "body");
  342.                 var updateStatusNode = this._utils.getFirstEltByTagAsNode(bodyNode, "update-status");
  343.                 updateStatus = updateStatusNode.getAttribute("code");
  344.             }
  345.             else
  346.             {
  347.                 var sourceNode = this._utils.getFirstEltByTagAsNode(root, "Source");
  348.                 var remoteVersion = this._utils.getFirstEltByTagAsNode(sourceNode, "Version").firstChild.nodeValue;
  349.                 var remoteDate = this._utils.getFirstEltByTagAsNode(sourceNode, "Date").firstChild.nodeValue;
  350.                 
  351.                 var currVersion = zoneManager.dataSource.version;
  352.                 var currDate = zoneManager.dataSource.date;
  353.         
  354.                 this._logger.log("FoxClocks_UpdateManager::_processXMLResponse(): from _noServerUpdateURL: remote version <" +
  355.                         remoteVersion + ">, remote date <" + remoteDate + ">, current version <" +
  356.                         currVersion + ">, current date <" + currDate + ">");
  357.                         
  358.                 if (remoteVersion > currVersion && remoteDate >= currDate)
  359.                     updateStatus = this.SERVER_UPDATE_NEW;
  360.             }
  361.             
  362.             if (updateStatus == this.SERVER_UPDATE_NEW)
  363.             {                        
  364.                 this._logger.log("FoxClocks_UpdateManager::_processXMLResponse(): updating...");
  365.         
  366.                 // AFM - dump zonesNode to string
  367.                 //
  368.                 const CHARSET = "UTF-8";            
  369.                 var serializer = CC["@mozilla.org/xmlextras/xmlserializer;1"].getService(CI.nsIDOMSerializer);
  370.                 
  371.                 var zonesNode = this._utils.getFirstEltByTagAsNode(doc, "zones");
  372.                 
  373.                 if (zonesNode == null)
  374.                     this._logger.log("FoxClocks_UpdateManager::_processXMLResponse(): zonesNode is null");
  375.                 var xmlString = this._utils.FC_XML_DEC + serializer.serializeToString(zonesNode, CHARSET);
  376.                                                        
  377.                 // AFM - write, create, truncate
  378.                 //
  379.                 var outStream = CC["@mozilla.org/network/file-output-stream;1"]
  380.                     .createInstance(CI.nsIFileOutputStream);
  381.                 outStream.init(this._updated_localDataFile, 0x02 | 0x08 | 0x20, 0664, 0);
  382.                         
  383.                 // AFM - do things in an i18n-safe way
  384.                 //        
  385.                 var outConvStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
  386.                                    .createInstance(Components.interfaces.nsIConverterOutputStream);
  387.                 
  388.                 outConvStream.init(outStream, CHARSET, 0, 0x0000);
  389.                 outConvStream.writeString(xmlString);
  390.                 outConvStream.close();
  391.                 
  392.                 this._logger.log("FoxClocks_UpdateManager::_processXMLResponse(): success - new data");
  393.                 retVal = "OK_NEW";
  394.             }
  395.             else
  396.             {
  397.                 this._logger.log("FoxClocks_UpdateManager::_processXMLResponse(): success - local data is current");
  398.                 retVal = "OK_NO";
  399.             }
  400.         }
  401.         catch (ex)
  402.         {
  403.             this._logger.log("FoxClocks_UpdateManager::_processXMLResponse(): error processing zone data: " + ex, this._logger.ERROR);
  404.             retVal = "ERROR";
  405.         }
  406.         
  407.         return retVal;
  408.     },
  409.     
  410.     // ====================================================================================
  411.     getLocalDataURL: function()
  412.     {
  413.         var protocolHandler = CC["@mozilla.org/network/protocol;1?name=file"].getService(CI.nsIFileProtocolHandler);
  414.         
  415.         var localDataFile = this._updated_localDataFile.exists() ? this._updated_localDataFile : this._builtin_localDataFile;
  416.         return protocolHandler.getURLSpecFromFile(localDataFile);
  417.     },
  418.     
  419.     // ====================================================================================
  420.     _getRemoteDataURL: function()
  421.     {
  422.         // AFM - dear reader, as you can see, nothing personal is sent in the update check.
  423.         // If you follow the code around, you'll see that a chunk of the server response
  424.         // is written to the file data/zones_update.xml, which is used by the zonemanager to
  425.         // rebuild its hash of time zone data. Your privacy is intact
  426.         //
  427.         var zoneManager = CC["@stemhaus.com/firefox/foxclocks/zonemanager;1"].
  428.             getService(CI.nsISupports).wrappedJSObject;
  429.             
  430.         var remoteDataURL = "http://www.stemhaus.com/firefox/foxclocks/data/"
  431.         remoteDataURL += (this._isDevel ? "zones-devel.cgi?devel=true&" : "zones.cgi?");
  432.         remoteDataURL += "client_id=FoxClocks";
  433.         remoteDataURL += "&client_version=" + this._utils.getFoxClocksVersion();
  434.         remoteDataURL += "&zone_id=*";
  435.         remoteDataURL += "&action=update-check";
  436.         remoteDataURL += "&data_source_id=" + zoneManager.dataSource.id;
  437.         remoteDataURL += "&data_source_version=" + zoneManager.dataSource.version;
  438.         remoteDataURL += "&data_source_date=" + zoneManager.dataSource.date;
  439.         remoteDataURL += "&year=" + new Date().getUTCFullYear();        
  440.                             
  441.         remoteDataURL = encodeURI(remoteDataURL);
  442.         
  443.         this._logger.log("FoxClocks_UpdateManager::_getRemoteDataURL(): url <" + remoteDataURL + ">");
  444.         return remoteDataURL;
  445.     },
  446.         
  447.     // ====================================================================================
  448.     // AFM - nsISupports
  449.     QueryInterface: function(aIID)
  450.     {
  451.         if( aIID.equals(CI.nsISupports) ||
  452.             aIID.equals(CI.nsIClassInfo) ||
  453.             aIID.equals(CI.nsIObserver))
  454.         {
  455.             return this;
  456.         }
  457.         
  458.         throw CR.NS_ERROR_NO_INTERFACE;
  459.     },
  460.   
  461.     // ====================================================================================
  462.     // AFM -  nsIClassInfo  
  463.     getInterfaces: function(aCount)
  464.     {            
  465.         var ifaces = new Array();
  466.         ifaces.push(CI.nsISupports);
  467.         ifaces.push(CI.nsIClassInfo);
  468.         ifaces.push(CI.nsIObserver);
  469.         aCount.value = ifaces.length;
  470.         return ifaces;
  471.     },
  472.   
  473.     // ====================================================================================
  474.     // AFM - nsIClassInfo  
  475.     getHelperForLanguage: function(aLanguage) { return null; },
  476.     get contractID() { return FoxClocks_UpdateManager.contractID; },
  477.     get classID() { return FoxClocks_UpdateManager.classID; },
  478.     get classDescription() { return FoxClocks_UpdateManager.classDescription; },
  479.     get implementationLanguage() { return CI.nsIProgrammingLanguage.JAVASCRIPT; },
  480.     get flags() { return CI.nsIClassInfo.SINGLETON; },
  481.   
  482.     // ====================================================================================
  483.     // AFM - nsIObserver
  484.     observe: function(aSubject, aTopic, aData)
  485.     {    
  486.         switch(aTopic)
  487.         {
  488.             case "xpcom-startup":
  489.                 // this is run very early, right after XPCOM is initialized, but before
  490.                 // user profile information is applied. Register ourselves as an observer
  491.                 // for 'profile-after-change' and 'quit-application'
  492.                 //            
  493.                 var obsSvc = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService);
  494.                 obsSvc.addObserver(this, "profile-after-change", false);
  495.                 obsSvc.addObserver(this, "quit-application", false);
  496.                 break;
  497.             
  498.             case "profile-after-change":
  499.                 // This happens after profile has been loaded and user preferences have been read.
  500.                 // startup code here
  501.                 this.startup();
  502.                 break;
  503.                 
  504.             case "quit-application":
  505.                 this.shutdown();
  506.                 var obsSvc = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService);
  507.                 obsSvc.removeObserver(this, "profile-after-change");
  508.                 obsSvc.removeObserver(this, "quit-application");
  509.                 var logService = CC["@stemhaus.com/firefox/foxclocks/logger;1"].getService(CI.nsISupports).wrappedJSObject;
  510.                 logService.log("shutdown: " + gCatContractID);
  511.                 break;
  512.         
  513.             default:
  514.                 this.onObserve(aSubject, aTopic, aData);
  515.         }
  516.     },
  517.     
  518.     // ====================================================================================
  519.     // AFM - http://www.mozilla.org/scriptable/js-components-status.html
  520.     //
  521.     get wrappedJSObject() { return this; }
  522. };
  523.  
  524. // ====================================================================================
  525. // constructors for objects we want to XPCOMify
  526. //
  527. var gXpComObjects = [FoxClocks_UpdateManager];
  528. var gCatObserverName = FoxClocks_UpdateManager.classDescription; // can be anything
  529. var gCatContractID = FoxClocks_UpdateManager.contractID;
  530.  
  531. // **********
  532. // AFM - generic registration code below. Useful URL: http://www.mozilla.org/projects/xpcom/book/cxc/html/weblock.html
  533. // **********
  534.  
  535. // ====================================================================================
  536. function NSGetModule(compMgr, fileSpec)
  537. {
  538.     gModule._catObserverName = gCatObserverName;
  539.     gModule._catContractId = gCatContractID;
  540.     
  541.     for (var i in gXpComObjects)
  542.         gModule._xpComObjects[i] = new FactoryHolder(gXpComObjects[i]);
  543.         
  544.     return gModule;
  545. }
  546.  
  547. // ====================================================================================
  548. function FactoryHolder(aObj)
  549. {
  550.     this.classID = aObj.classID;
  551.     this.contractID = aObj.contractID;
  552.     this.className  = aObj.classDescription;
  553.     this.factory =
  554.     {
  555.         createInstance: function(aOuter, aIID)
  556.         {
  557.             if (aOuter)
  558.                 throw CR.NS_ERROR_NO_AGGREGATION;
  559.                 
  560.             return (new this.constructor).QueryInterface(aIID);
  561.         }
  562.     };
  563.     
  564.     this.factory.constructor = aObj;
  565. }
  566.  
  567. // ====================================================================================
  568. var gModule =
  569. {
  570.     _xpComObjects: {},
  571.     _catObserverName: null,
  572.     _catContractId: null,
  573.     
  574.     // ====================================================================================
  575.     registerSelf: function(aComponentManager, aFileSpec, aLocation, aType)
  576.     {
  577.         aComponentManager.QueryInterface(CI.nsIComponentRegistrar);
  578.         for (var key in this._xpComObjects)
  579.         {
  580.             var obj = this._xpComObjects[key];
  581.             aComponentManager.registerFactoryLocation(obj.classID, obj.className,
  582.             obj.contractID, aFileSpec, aLocation, aType);
  583.         }
  584.         
  585.         var catman = CC["@mozilla.org/categorymanager;1"].getService(CI.nsICategoryManager);
  586.         catman.addCategoryEntry("xpcom-startup", this._catObserverName, this._catContractId, true, true);
  587.         catman.addCategoryEntry("xpcom-shutdown", this._catObserverName, this._catContractId, true, true);
  588.     },
  589.  
  590.     // ====================================================================================
  591.     unregisterSelf: function(aCompMgr, aFileSpec, aLocation)
  592.     {
  593.         var catman = CC["@mozilla.org/categorymanager;1"].getService(CI.nsICategoryManager);
  594.         catman.deleteCategoryEntry("xpcom-startup", this._catObserverName, true);
  595.         catman.deleteCategoryEntry("xpcom-shutdown", this._catObserverName, true);
  596.         
  597.         aComponentManager.QueryInterface(CI.nsIComponentRegistrar);
  598.         for (var key in this._xpComObjects)
  599.         {
  600.             var obj = this._xpComObjects[key];
  601.             aComponentManager.unregisterFactoryLocation(obj.classID, aFileSpec);
  602.         }
  603.     },
  604.  
  605.     // ====================================================================================
  606.     getClassObject: function(aComponentManager, aCID, aIID)
  607.     {
  608.         if (!aIID.equals(CI.nsIFactory))
  609.             throw CR.NS_ERROR_NOT_IMPLEMENTED;
  610.         
  611.         for (var key in this._xpComObjects)
  612.         {
  613.             if (aCID.equals(this._xpComObjects[key].classID))
  614.                 return this._xpComObjects[key].factory;
  615.         }
  616.     
  617.         throw CR.NS_ERROR_NO_INTERFACE;
  618.     },
  619.  
  620.     // ====================================================================================
  621.     canUnload: function(aComponentManager) { return true; }
  622. };